/**
 * <p>Title: Cell Class</p>
 * <p>Description: CS 343 Project</p>
 * @author Quintin Stolpe
 * @email quintin.stolpe@my.uwrf.edu
 * @date Dec 3 2012
*/

package maze;


import java.awt.Color;
import java.awt.Event;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public final class Pacman extends java.applet.Applet implements Runnable
{

	private static final int P_BUTTON = 112;
	private static final int SPACE_BAR = 32;
	private static final int DOWN_ARROW_BUTTON = 1005;
	private static final int UP_ARROW_BUTTON = 1004;
	private static final int RIGHT_ARROW_BUTTON = 1007;
	private static final int LEFT_ARROW_BUTTON = 1006;

	Graphics gBuf; 
	Image imgBuf;
	Graphics gBufMaze;
	Image mazeBuf;
	public int PACMAN_SIZE = 36;

	Thread updateThread; // thread in which the game will run
	long startTime;


	int x = 0; 
	int dx[] = { 0, 0 };
	int y = 0; 
	int dy[] = { 0, 0 };

	int curCol; 
	int curRow;
	int nextX; 
	int nextY; 

	int mouthStartAngle = 180; 

	// This array describes each "cell" of the screen
	// Cells labeled 0 are open and those with a 1 are
	// walls.
	int[][] mazeArray;

	private int SPEED = 12;
	private int MAX_X = 800;
	
	private int MAX_Y = 600;
	

	private int xOffset = 0; // used to center screen
	private int yOffset =0; // used to center screen
	private Maze maze = null;
	private Ghost ghost = new Ghost();
	private int clearGraphics = 2;
	
	private GameInfo[] gameInfoArray;
	private int gameInfoCount = 0;
	private int pellets = 0;
	
	class EventHandler implements KeyListener
	{

		
		public void keyTyped(KeyEvent e)
		{
			
			
		}

		
		public void keyPressed(KeyEvent e)
		{
			
			myKeyDown(e.getKeyCode());
			
		}

		
		public void keyReleased(KeyEvent e)
		{
			
			
		}
		
	}
	public void getMainGraphics() // Load and process the most common graphics
	{
		MediaTracker tracker;
		int i = 0;

		tracker = new MediaTracker(this);

		

		try
		{
			tracker.waitForAll();
		}
		catch (InterruptedException e)
		{
		}

	}
	public boolean processMouseEvent(Event e)
	{
		requestFocus();
		return false;
	}
	public void init()
	{
		// Make the applet window the size we want it to be
		resize(MAX_X, MAX_Y);

		// Load the images used from the web
		getMainGraphics();

	
		System.gc();

		addKeyListener(new EventHandler());
		// Make a black background
		setBackground(Color.gray);

		// Set up double-buffered graphics.
		// This allows us to draw without flickering.
		imgBuf = createImage(MAX_X, MAX_Y);
		gBuf = imgBuf.getGraphics();
		
		mazeBuf = createImage(MAX_X,MAX_Y);
		gBufMaze = mazeBuf.getGraphics();
		
		gameInfoArray = new GameInfo[4];
		gameInfoArray[0]= new GameInfo(48,7,7,9,9,16,8);
		gameInfoArray[1]= new GameInfo(36,9,9,12,12,12,6);
		gameInfoArray[2]= new GameInfo(24,13,13,17,17,8,6);
		gameInfoArray[3]= new GameInfo(18,17,17,23,23,9,6);
		

		doResetMaze();

	}

	public boolean myKeyDown(int key)
	{

		// This method handles key presses.
	

		if (key == KeyEvent.VK_LEFT)
		{
			if (dx[0] == 0 && dy[0] == 0)
			{
				dx[0] = -SPEED;
				dx[1] = -SPEED;
				dy[0] = 0;
				dy[1] = 0;
			}
			else
			{
				dx[1] = -SPEED;
				dy[1] = 0;
			}
			return false;
		}
		if (key == KeyEvent.VK_RIGHT)
		{
			if (dx[0] == 0 && dy[0] == 0)
			{
				dx[0] = SPEED;
				dx[1] = SPEED;
				dy[0] = 0;
				dy[1] = 0;
			}
			else
			{
				dx[1] = SPEED;
				dy[1] = 0;
			}
			return false;
		}

		if (key == KeyEvent.VK_UP)
		{
			if (dx[0] == 0 && dy[0] == 0)
			{
				dx[0] = 0;
				dx[1] = 0;
				dy[0] = -SPEED;
				dy[1] = -SPEED;
			}
			else
			{
				dx[1] = 0;
				dy[1] = -SPEED;
			}

			return false;
		}
		if (key == KeyEvent.VK_DOWN)
		{
			if (dx[0] == 0 && dy[0] == 0)
			{
				dx[0] = 0;
				dx[1] = 0;
				dy[0] = SPEED;
				dy[1] = SPEED;
			}
			else
			{
				dx[1] = 0;
				dy[1] = SPEED;
			}
			return false;
		}


		return false;
	}

	public boolean myKeyUp(int key)
	{
		
		return false;
	}

	
	public void paint(Graphics g) // Draw stuff
	{
	
		update(g);
	}

	public void run()
	{
		// This is the most important method.  It loops over and
		// over again as the game is running.  It makes the calls
		// that move things and then draw them.

		int mouthOpenAngle = 20;
		int dMouthOpenAngle = 5;
		int maxMouthOpenAngle = 100;
		int minMouthOpenAngle = 1;

		nextX = 9 * PACMAN_SIZE;
		nextY = 9 * PACMAN_SIZE;

		x = nextX;
		y = nextY;

		boolean hitWall = false;

		int i;

		//		try to grab the keyboard focus.
		requestFocus();

		while (updateThread != null)
		{
			
			// DRAW 
			
			
			curCol = (x + PACMAN_SIZE / 2) / PACMAN_SIZE;
			curRow = (y + PACMAN_SIZE / 2) / PACMAN_SIZE;

			if (mazeArray[curRow][curCol] == Maze.PELLET)
			{
				pellets--;
				mazeArray[curRow][curCol] = Maze.EMPTY_SQUARE;
				gBufMaze.setColor(Color.gray);
				gBufMaze.fillRect(curCol*PACMAN_SIZE,curRow*PACMAN_SIZE,PACMAN_SIZE,PACMAN_SIZE);
			}

			
			

			for (i = 1; i >= 0; i--)
			{
				nextX = x + dx[i];
				nextY = y + dy[i];
				hitWall = false;

				int col;
				int row;
				hitWall = doDidWeHitAWall(hitWall);
				if (!hitWall)
				{
					if (i == 1)
					{
						dx[0] = dx[1];
						dy[0] = dy[1];
					}
					break;
				}

			}

			doDrawMouth();
			//Move PacMan
			doMovePacman(nextX, nextY, hitWall, i);
			// Make the mouth chomp chomp
			{
				mouthOpenAngle = mouthOpenAngle + dMouthOpenAngle;

				if (mouthOpenAngle > maxMouthOpenAngle)
				{
					mouthOpenAngle = maxMouthOpenAngle;
					dMouthOpenAngle = -10;
				}
				if (mouthOpenAngle < minMouthOpenAngle)
				{
					mouthOpenAngle = minMouthOpenAngle;
					dMouthOpenAngle = 5;
				}
			}
			ghost.move(curRow, curCol, maze.myGraph);
			
			doComputeSleepTime();
			startTime = System.currentTimeMillis() + 40;
			
			if (clearGraphics>0)
			{
				gBuf.setClip(null);
				gBuf.drawImage(mazeBuf,0,0,MAX_X,MAX_Y,this);
				clearGraphics --;
			}
			else
			{
				Rectangle rect = new Rectangle(x-SPEED,y-SPEED,PACMAN_SIZE + SPEED + SPEED,PACMAN_SIZE + SPEED + SPEED);
				rect.add(new Rectangle(ghost.getY()-ghost.getSpeed(),ghost.getX()-ghost.getSpeed(),PACMAN_SIZE+ghost.getSpeed()+ghost.getSpeed(),PACMAN_SIZE+ghost.getSpeed()+ghost.getSpeed()));
				gBuf.setClip(rect);
			}
			
		
			
			gBuf.drawImage(mazeBuf,0,0,MAX_X,MAX_Y,this);
			//set the drawing color
			gBuf.setColor(Color.yellow);
			
			// draw a PacMan
			gBuf.fillArc(
				x,
				y,
				PACMAN_SIZE,
				PACMAN_SIZE,
				mouthStartAngle + mouthOpenAngle / 2,
				360 - mouthOpenAngle);
			repaint();
			// draw ghost
			
			gBuf.setClip(new Rectangle(ghost.getY()-ghost.getSpeed(),ghost.getX()-ghost.getSpeed(),PACMAN_SIZE+ghost.getSpeed()+ghost.getSpeed(),PACMAN_SIZE+ghost.getSpeed()+ghost.getSpeed()));
			gBuf.drawImage(mazeBuf,0,0,MAX_X,MAX_Y,this);
						 
			gBuf.setColor(Color.white);
			gBuf.fillRect(ghost.getY(), ghost.getX(), PACMAN_SIZE, PACMAN_SIZE);
			
			if (Math.abs(x-ghost.getY()) < PACMAN_SIZE/2 && Math.abs(y-ghost.getX()) < PACMAN_SIZE/2)
			{
				resetLocations();
			}

			// repaint() will call paint(Graphics) which will call update(Graphics)
			
			if (pellets < 1)
			{
				doResetMaze();
			}
			repaint();
		}
	}

	private boolean doDidWeHitAWall(boolean hitWall)
	{
		// Process each "cell" of the maze and paint it correctly
		for (int xCorner = 0;
			xCorner < PACMAN_SIZE;
			xCorner += PACMAN_SIZE - 1)
		{
			for (int yCorner = 0;
				yCorner < PACMAN_SIZE;
				yCorner += PACMAN_SIZE - 1)
			{
				int col = (nextX + xCorner) / PACMAN_SIZE;
				int row = (nextY + yCorner) / PACMAN_SIZE;
				if (row < mazeArray.length && col < mazeArray[0].length)
				{
					if (mazeArray[row][col] == Maze.WALL)
					{
						hitWall = true;
						break;
					}
				}
			}
		}
		return hitWall;
	}

	private void doComputeSleepTime()
	{
		{

			try
			{
				
				long sleepTime =
					Math.max(startTime - System.currentTimeMillis(), 10);
				Thread.sleep(sleepTime);
			}
			catch (InterruptedException e)
			{
			}
		}
	}

	private void doMovePacman(int nextX, int nextY, boolean hitWall, int i)
	{
		{
			if (!hitWall)
			{
				x = nextX;
				y = nextY;
			}
			else
			{
				dx[0] = 0;
				dx[1] = 0;
				dy[0] = 0;
				dy[1] = 0;
			}

			// Don't let him go off the sides of the screen
			if (x > MAX_X - PACMAN_SIZE)
			{
				x = MAX_X - PACMAN_SIZE;
				dx[0] = 0;
				dx[1] = 0;
			}
			if (x < 0)
			{
				x = 0;
				dx[0] = 0;
				dx[1] = 0;
			}

			// Don't let him go off the top or bottom of the screen
			if (y > MAX_Y - PACMAN_SIZE)
			{
				y = MAX_Y - PACMAN_SIZE;
				dy[i] = 0;
				dy[1] = 0;
			}
			if (y < 0)
			{
				y = 0;
				dy[i] = 0;
				dy[1] = 0;
			}
		}
	}

	private void doDrawMouth()
	{
		{
			if (dx[0] > 0)
			{
				mouthStartAngle = 0;
			}
			else if (dx[0] < 0)
			{
				mouthStartAngle = 180;
			}
			else if (dy[0] > 0)
			{
				mouthStartAngle = 270;
			}
			else if (dy[0] < 0)
			{
				mouthStartAngle = 90;
			}
		}
	}

	private int doDrawMaze()
	{
		int row;
		int col;
		int pellets;

		// draw maze

		gBufMaze.setColor(Color.gray);
		gBufMaze.fillRect(0,0,MAX_X,MAX_Y);
		gBufMaze.setColor(Color.green);

		pellets = 0;

		for (row = 0; row < mazeArray.length; row++)
		{
			for (col = 0; col < mazeArray[row].length; col++)
			{
				if (mazeArray[row][col] == Maze.WALL)
				{
					gBufMaze.setColor(Color.green);
					gBufMaze.fillRect(
						col * PACMAN_SIZE,
						row * PACMAN_SIZE,
						PACMAN_SIZE,
						PACMAN_SIZE);
				}
				else if (mazeArray[row][col] == Maze.PELLET)
				{
					pellets++;
					gBufMaze.setColor(Color.yellow);
					//gBuf.fillArc(col * PACMAN_SIZE+PACMAN_SIZE/2 - 5, row * PACMAN_SIZE+PACMAN_SIZE/2 -5, 10,10,0,360);
					gBufMaze.fillRect(
						col * PACMAN_SIZE + PACMAN_SIZE / 2,
						row * PACMAN_SIZE + PACMAN_SIZE / 2,
						PACMAN_SIZE / 5,
						PACMAN_SIZE / 5);
				}
			}
		}

		return pellets;
	}

	private void doResetMaze()
	{
		
		PACMAN_SIZE = gameInfoArray[gameInfoCount].getPacmanSize();
		SPEED = gameInfoArray[gameInfoCount].getPacmanSpeed();
		
		maze = new Maze(gameInfoArray[gameInfoCount].getMaxMaxWidth(),gameInfoArray[gameInfoCount].getMaxMazeHeight());
		mazeArray = maze.generateMazeArray();
		xOffset = (MAX_X-(mazeArray[0].length*PACMAN_SIZE))/2;
		yOffset = (MAX_Y-(mazeArray.length*PACMAN_SIZE))/2;
		ghost.setSpeed(gameInfoArray[gameInfoCount].getGhostSpeed());
		ghost.setSize(PACMAN_SIZE);
		ghost.setMaze(mazeArray);
		resetLocations();
		
		pellets = doDrawMaze();
		clearGraphics = 2;
		gameInfoCount++;
		gameInfoCount = gameInfoCount%gameInfoArray.length;
		
	}

	private void resetLocations()
	{
		ghost.setXY(3, 3);
		
		nextX = 9 * PACMAN_SIZE;
		nextY = 9 * PACMAN_SIZE;
		x = nextX;
		y = nextY;

		dx[0] = 0;
		dy[0] = 0; 
		dx[1] = 0; 
		dy[1] = 0; 
		
		mouthStartAngle = 180; // direction that pacman is pointing

		clearGraphics = 2;

	}

	// This method is called when the applet is run.
	// It initiallizes the thread and gets things going.
	public void start()
	{
		if (updateThread == null)
		{
			updateThread = new Thread(this, "Game");
			updateThread.start();
			startTime = System.currentTimeMillis();
		}
	}

	// This method is called when the applet is stopped. 
	public void stop()
	{
		updateThread = null;
	}

	public void update(Graphics g)
	{
	
		// Called by the run() method
		if(clearGraphics>0)
		{
			g.setColor(Color.gray);
			g.fillRect(0,0,MAX_X,MAX_Y);
			
			g.drawImage(imgBuf, xOffset,yOffset, this);
			clearGraphics--;
		}
		g.drawImage(imgBuf, xOffset, yOffset, this);
		
	}
}
